package com.zwps.biz.domain.service.impl;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import javax.imageio.ImageIO;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zwps.biz.dal.db.dao.FileRecordBizSettingDAO;
import com.zwps.biz.dal.db.dao.FileRecordDAO;
import com.zwps.biz.dal.db.dao.FileRecordVideoDAO;
import com.zwps.biz.dal.db.object.FileRecordBizSettingDO;
import com.zwps.biz.dal.db.object.FileRecordDO;
import com.zwps.biz.dal.db.object.FileRecordVideoDO;
import com.zwps.biz.domain.config.properties.FileProcessConfigProperties;
import com.zwps.biz.domain.config.properties.FileProcessPictureConfigProperties;
import com.zwps.biz.domain.config.properties.FileProcessVideoConfigProperties;
import com.zwps.biz.domain.enums.FileClassEnum;
import com.zwps.biz.domain.enums.FileProcessRepeatStrategyEnum;
import com.zwps.biz.domain.enums.FileUseTypeEnum;
import com.zwps.biz.domain.extra.rabbitmq.message.PictureCompressConfigMessage;
import com.zwps.biz.domain.extra.rabbitmq.message.PictureCompressMessage;
import com.zwps.biz.domain.extra.rabbitmq.message.VideoCoverCaptureConfigMessage;
import com.zwps.biz.domain.extra.rabbitmq.message.VideoProcessMessage;
import com.zwps.biz.domain.service.FileManageService;
import com.zwps.biz.domain.service.FileProcessService;
import com.zwps.common.core.exception.ServeException;
import com.zwps.common.minio.config.MinioConfigProperties;
import com.zwps.common.tool.ffmpeg.FFmpegTool;
import com.zwps.common.tool.ffmpeg.MultimediaInfo;
import com.zwps.common.tool.ffmpeg.MultimediaInfo.AudioInfo;
import com.zwps.common.tool.ffmpeg.MultimediaInfo.VideoInfo;

import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import kotlin.jvm.functions.Function2;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@AllArgsConstructor
public class FileProcessServiceImpl implements FileProcessService {

    private ObjectMapper objectMapper;

    private FileProcessConfigProperties fileProcessConfig;

    private FileRecordBizSettingDAO fileRecordBizSettingDAO;

    private FileManageService fileManageService;

    private FileRecordDAO fileRecordDAO;

    private MinioConfigProperties minioConfig;

    private FileRecordVideoDAO fileRecordVideoDAO;

    private final String PICTURE_COMPRESS_FILE_SUFFIX = "jpg";

    private FileRecordDO pictureCompressBefore(PictureCompressMessage msg, Function2<Integer, Integer, Void> func) {
        FileRecordDO fileRecordDO = fileRecordDAO.selectById(msg.getFileId());
        if (fileRecordDO == null) {
            throw new ServeException("文件不存在：" + msg.getFileId());
        }

        if (!FileClassEnum.IMAGE.getCode().equals(fileRecordDO.getFileClass())) {
            throw new ServeException("文件不是图片格式，文件id：" + msg.getFileId());
        }

        LambdaQueryWrapper<FileRecordDO> fileRecordQuery = Wrappers.lambdaQuery(FileRecordDO.class);
        fileRecordQuery.eq(FileRecordDO::getParentId, msg.getFileId());
        fileRecordQuery.eq(FileRecordDO::getUseType, FileUseTypeEnum.IMAGE_COMPRESS.getCode());
        fileRecordQuery.last(" limit 1");
        boolean exist = fileRecordDAO.exists(fileRecordQuery);

        // 检测图片是否存在压缩图片以及如何处理
        if (exist) {
            Integer repeatStrategy = msg.getRepeatStrategy();
            if (repeatStrategy == null) {
                throw new ServeException("该图片已存在压缩图片，重复策略为：" + repeatStrategy);
            }
            if (repeatStrategy.equals(FileProcessRepeatStrategyEnum.SKIP.getCode())) {
                log.info("该图片已存在压缩图片，重复策略为：{}", FileProcessRepeatStrategyEnum.SKIP.getName());
                return null;
            }
            if (repeatStrategy.equals(FileProcessRepeatStrategyEnum.REPLACE.getCode())) { // 覆盖，把存在的压缩图片删除
                LambdaQueryWrapper<FileRecordDO> fileRecordDelete = Wrappers.lambdaQuery(FileRecordDO.class);
                fileRecordDelete.eq(FileRecordDO::getParentId, msg.getFileId());
                fileRecordDelete.eq(FileRecordDO::getUseType, FileUseTypeEnum.IMAGE_COMPRESS.getCode());
                if (fileRecordDAO.delete(fileRecordDelete) < 1) {
                    throw new ServeException("该图片已存在压缩图片，删除历史压缩图片失败");
                }
            }
        }

        PictureCompressConfigMessage pictureCompress = msg.getConfig();
        if (pictureCompress != null) {
            func.invoke(pictureCompress.getPictureCompressWidth(), pictureCompress.getPictureCompressHeight());
            return fileRecordDO;
        }

        LambdaQueryWrapper<FileRecordBizSettingDO> query = Wrappers.lambdaQuery(FileRecordBizSettingDO.class)
                .eq(FileRecordBizSettingDO::getBizTable, msg.getBizTable());
        FileRecordBizSettingDO bizSettingDO = fileRecordBizSettingDAO.selectOne(query);
        if (bizSettingDO != null) {
            if (bizSettingDO.getPictureCompress() == null || 0 == bizSettingDO.getPictureCompress()) {
                return null;
//                throw new ServeException("业务表配置为不开启图片压缩");
            }
            if (1 == bizSettingDO.getPictureCompress()) {
                func.invoke(bizSettingDO.getPictureCompressWidth(), bizSettingDO.getPictureCompressHeight());
                return fileRecordDO;
            }
        }

        FileProcessPictureConfigProperties pictureConfig = fileProcessConfig.getPicture();
        if (pictureConfig == null) {
            return null;
//            throw new ServeException("系统未配置图片压缩");
        }
        if (pictureConfig.getCompress() == null || 1 != pictureConfig.getCompress()) {
            return null;
//            throw new ServeException("系统配置为不开启图片压缩");
        }
        func.invoke(pictureConfig.getCompressWidth(), pictureConfig.getCompressHeight());
        return fileRecordDO;
    }

    private String processFileUrl(FileRecordDO fileRecord) {
        String url = minioConfig.getInternalUrl() + "/" + fileRecord.getFilePath();
        url = url.replaceAll("\\\\", "/");
        url = url.replaceAll(" ", "%20");
        return url;
    }

    private String generateProcessFileName(String fileName, String process, String fileSuffix) {
        return fileName.substring(0, fileName.indexOf(".")) + "_" + process + "." + fileSuffix;
    }

    @Override
    public void pictureCompress(String message) throws Exception {
        PictureCompressMessage msg = objectMapper.readValue(message, PictureCompressMessage.class);

        final Integer[] widthHeight = new Integer[2];
        FileRecordDO fileRecordDO = pictureCompressBefore(msg, (width, height) -> {
            widthHeight[0] = width;
            widthHeight[1] = height;
            return null;
        });
        if (fileRecordDO == null) {
            return;
        }
        Integer compressWidth = widthHeight[0];
        Integer compressHeight = widthHeight[1];
        if (compressWidth == null || compressWidth < 1) {
            throw new ServeException("错误压缩后的图片宽度配置，宽度为：" + compressWidth);
        }
        if (compressHeight == null || compressHeight < 1) {
            throw new ServeException("错误压缩后的图片高度配置，高度为：" + compressHeight);
        }

        String url = processFileUrl(fileRecordDO);
        URL URL = new URL(url);
        URLConnection conn = URL.openConnection();
        conn.setDoOutput(true);

        BufferedImage srcImage = ImageIO.read(conn.getInputStream());
//          BufferedImage srcImage = ImageIO.read(URI.create(url).toURL());
        int width = srcImage.getWidth();
        int height = srcImage.getHeight();
//          msg.setPictureSize(width + "x" + height);

        // 固定压缩后的图片宽度为300，高度按原来的宽高比例计算压缩后的高度
        height = (int) ((((double) height / (double) width)) * (double) compressHeight);
        width = compressWidth;
        // 构造一个类型为预定义图像类型之一的 BufferedImage
        BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 绘制图像 getScaledInstance表示创建此图像的缩放版本，返回一个新的缩放版本Image，按指定的width height呈现图像
        // Image.SCALE_SMOOTH，选择图像平滑度比缩放速度具有更高优先级的图像缩放算法。
        tag.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(tag, PICTURE_COMPRESS_FILE_SUFFIX, baos);
        String oldFileName = new File(url).getName();
        String newFileName = generateProcessFileName(oldFileName, "thumb", PICTURE_COMPRESS_FILE_SUFFIX);
        InputStream stream = new ByteArrayInputStream(baos.toByteArray());
        fileManageService.uploda(stream, msg.getBizTable(), newFileName, PICTURE_COMPRESS_FILE_SUFFIX,
                (long) baos.size(), FileUseTypeEnum.IMAGE_COMPRESS, fileRecordDO.getId());

    }

    private VideoCoverCaptureConfigMessage videoProcessCoverCaptureBefore(VideoProcessMessage msg,
            FileRecordDO fileRecord) {

        VideoCoverCaptureConfigMessage config = msg.getCoverCaptureConfig();
        if (config == null) {
            LambdaQueryWrapper<FileRecordBizSettingDO> query = Wrappers.lambdaQuery(FileRecordBizSettingDO.class)
                    .eq(FileRecordBizSettingDO::getBizTable, msg.getBizTable());
            FileRecordBizSettingDO bizSettingDO = fileRecordBizSettingDAO.selectOne(query);
            if (bizSettingDO != null && bizSettingDO.getVideoCover() != null) {
                if (0 == bizSettingDO.getVideoCover()) {
                    return null;
                }
                if (1 == bizSettingDO.getVideoCover()) {
                    config = new VideoCoverCaptureConfigMessage();
                    config.setCoverHeight(bizSettingDO.getVideoCoverCaptureWidth());
                    config.setCoverWidth(bizSettingDO.getVideoCoverCaptureHeight());
                    config.setCaptureStrategy(bizSettingDO.getVideoCoverCaptureStrategy());
                    config.setRepeatStrategy(bizSettingDO.getVideoCoverCaptureRepeatStrategy());
                }
            } else if (fileProcessConfig.getVideo() != null && fileProcessConfig.getVideo().isCover()) {
                FileProcessVideoConfigProperties c = fileProcessConfig.getVideo();
                config = new VideoCoverCaptureConfigMessage();
                config.setCoverHeight(c.getCoverCaptureWidth());
                config.setCoverWidth(c.getCoverCaptureHeight());
                config.setCaptureStrategy(c.getCoverCaptureStrategy());
                config.setRepeatStrategy(c.getCoverCaptureRepeatStrategy());
            }
        }
        if (config == null) {
            return null;
        }
        msg.setCoverCaptureConfig(config);
        LambdaQueryWrapper<FileRecordDO> fileRecordQuery = Wrappers.lambdaQuery(FileRecordDO.class);
        fileRecordQuery.eq(FileRecordDO::getParentId, fileRecord.getId());
        fileRecordQuery.eq(FileRecordDO::getUseType, FileUseTypeEnum.VIDEO_COVER.getCode());
        fileRecordQuery.last(" limit 1");
        boolean exist = fileRecordDAO.exists(fileRecordQuery);

        // 检测视频是否存在封面，存在按照处理策略
        if (exist) {
            Integer repeatStrategy = config.getRepeatStrategy();
            if (repeatStrategy == null) {
                throw new ServeException("该视频已存在视频封面，重复策略为：" + repeatStrategy);
            }
            if (repeatStrategy.equals(FileProcessRepeatStrategyEnum.SKIP.getCode())) {
                log.info("该视频已存在视频封面，重复策略为：{}", FileProcessRepeatStrategyEnum.SKIP.getName());
                return null;
            }
            if (repeatStrategy.equals(FileProcessRepeatStrategyEnum.REPLACE.getCode())) { // 覆盖，把存在的封面图片删除
                LambdaQueryWrapper<FileRecordDO> fileRecordDelete = Wrappers.lambdaQuery(FileRecordDO.class);
                fileRecordDelete.eq(FileRecordDO::getParentId, fileRecord.getId());
                fileRecordDelete.eq(FileRecordDO::getUseType, FileUseTypeEnum.VIDEO_COVER.getCode());
                if (fileRecordDAO.delete(fileRecordDelete) < 1) {
                    throw new ServeException("该视频已存在封面图片，删除历史封面图片失败");
                }
            }
        }
        // TODO: 暂时不处理VideoCaptureStrategyEnum这个策略
        Integer width = config.getCoverWidth();
        Integer height = config.getCoverHeight();
        if (width != null && width < 1) {
            throw new ServeException("错误视频封面图片宽度配置，宽度为：" + width);
        }
        if (height != null && height < 1) {
            throw new ServeException("错误视频封面图片高度配置，高度为：" + height);
        }
        return config;
    }

    private void videoProcessCoverCapture(VideoProcessMessage msg, VideoCoverCaptureConfigMessage config,
            FileRecordDO fileRecord) throws Exception {

        File tempFilePath = new File(fileProcessConfig.getTempPath());
        if (!tempFilePath.exists()) {
            if (!tempFilePath.mkdirs()) {
                throw new ServeException("创建目录失败：" + tempFilePath.getPath());
            }
        }

        String tempFileName = generateProcessFileName(fileRecord.getFileName(), "cover", PICTURE_COMPRESS_FILE_SUFFIX);
        File tempFile = new File(fileProcessConfig.getTempPath() + File.separator + tempFileName);
        String tempFileAbsolutePath = tempFile.getAbsolutePath();
        FFmpegTool.makeScreenCut(fileProcessConfig.getFfmepgPath(), processFileUrl(fileRecord), tempFileAbsolutePath);
        if (!tempFile.exists()) {
            throw new ServeException("处理视频生成封面图片失败，预期封面图片路径：" + tempFileAbsolutePath);
        }
        try (FileInputStream fileInputStream = new FileInputStream(tempFile);) {

            fileManageService.uploda(fileInputStream, msg.getBizTable(), tempFileName, PICTURE_COMPRESS_FILE_SUFFIX,
                    (long) fileInputStream.available(), FileUseTypeEnum.VIDEO_COVER, fileRecord.getId());
        } finally {
            tempFile.deleteOnExit();
        }
    }

    /**
     * 视频处理-解析视频信息
     * 
     * @Title: videoProcessParseInfo
     * @Description:
     * @param fileRecord void
     * @author lyh
     * @throws Exception
     * @date 2023年10月26日 下午2:37:11
     */
    private void videoProcessParseInfo(FileRecordDO fileRecord) throws Exception {

        LambdaQueryWrapper<FileRecordVideoDO> query = Wrappers.lambdaQuery(FileRecordVideoDO.class)
                .eq(FileRecordVideoDO::getFileId, fileRecord.getId());
        FileRecordVideoDO fileRecordVideoDO = fileRecordVideoDAO.selectOne(query);
        if (fileRecordVideoDO != null) {
            log.info("Video process parse info already exist, skip parse.");
            return;
        }

        MultimediaInfo multimediaInfo = FFmpegTool.parseVideoInfo(fileProcessConfig.getFfmepgPath(),
                processFileUrl(fileRecord));

        FileRecordVideoDO video = new FileRecordVideoDO();
        video.setVideoDuration(multimediaInfo.getDuration());
        video.setVideoDurationChinese(
                DateUtil.formatBetween(multimediaInfo.getDuration(), BetweenFormatter.Level.SECOND));
        VideoInfo videoInfo = multimediaInfo.getVideo();
        if (videoInfo != null) {
            video.setVideoWidth(videoInfo.getWidth());
            video.setVideoHeight(videoInfo.getHeight());
            video.setVideoBitRate(videoInfo.getBitRate());
            video.setVideoFrameRate(String.valueOf(videoInfo.getFrameRate()));
            video.setVideoDecoder(videoInfo.getDecoder());
        }
        AudioInfo audioInfo = multimediaInfo.getAudio();
        if (audioInfo != null) {
            video.setAudioBitRate(audioInfo.getBitRate());
            video.setAudioChannel(audioInfo.getChannels());
            video.setAudioDecoder(audioInfo.getDecoder());
            video.setAudioSamplingRate(audioInfo.getSamplingRate());
        }
        video.setFileId(fileRecord.getId());
        if (fileRecordVideoDAO.insert(video) < 1) {
            log.error("Video process parse info insert to db error");
        }
    }

    @Override
    public void videoProcess(String message) throws Exception {
        VideoProcessMessage msg = objectMapper.readValue(message, VideoProcessMessage.class);

        FileRecordDO fileRecordDO = fileRecordDAO.selectById(msg.getFileId());
        if (fileRecordDO == null) {
            throw new ServeException("文件不存在：" + msg.getFileId());
        }

        if (!FileClassEnum.VIDEO.getCode().equals(fileRecordDO.getFileClass())) {
            throw new ServeException("文件不是" + FileClassEnum.VIDEO.getName() + "格式，文件id：" + msg.getFileId());
        }
        // 解析视频文件的流媒体信息并入库
        videoProcessParseInfo(fileRecordDO);

        // 后续视频有其它的处理，在这个位置在新增处理

        // 获取视频封面的相关配置
        VideoCoverCaptureConfigMessage coverCaptureConfig = videoProcessCoverCaptureBefore(msg, fileRecordDO);
        if (coverCaptureConfig != null) {
            videoProcessCoverCapture(msg, coverCaptureConfig, fileRecordDO);
        }
    }

}
